# -*- coding: utf-8 -*-
# standard
import os
import sys
import platform
import re
import time
import json
import atexit
from typing import Callable
from datetime import datetime
from contextlib import asynccontextmanager

# third
import uvicorn
from fastapi import FastAPI
from fastapi import WebSocket
from fastapi.routing import APIRoute
from fastapi import Depends
from fastapi import Query as QueryParam
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import Response
from fastapi.responses import JSONResponse
from fastapi.responses import FileResponse
from fastapi.responses import RedirectResponse
from fastapi.responses import HTMLResponse
from fastapi.responses import RedirectResponse
from fastapi.requests import Request
from fastapi.exceptions import HTTPException
from fastapi.staticfiles import StaticFiles
from fastapi.exceptions import StarletteHTTPException
from fastapi.templating import Jinja2Templates
from fastapi.security import OAuth2PasswordRequestForm
from fastapi.openapi.docs import get_swagger_ui_html

# local
_P_PATH = os.path.dirname(os.path.realpath(__file__))
if _P_PATH not in sys.path:
    sys.path.append(_P_PATH)
from functions import *
from views.iot_view import router as iot_router
from views.setting_view import router as setting_router
from views.collect_view import router as collect_router
from toolbox.common_tools import INCLUDE_SCHEMA
from toolbox.common_tools import logger
from toolbox.common_tools import get_fastapi_real_ip
from toolbox.common_tools import check_env
from toolbox.common_tools import DEBUG
begin = datetime.now()
print(f"view加载完毕:{(datetime.now() - begin).total_seconds()}") 


"""
@Title:  
@File: main_server.py
@Author: walle 2022年10日30日 23时13分01秒
@Version: 1.0.0
@Desc: 
"""


ENV = check_env() 
if ENV != "dev": # 非开发环境 关闭文档
    app = FastAPI(docs_url=None, redoc_url=None) # docs_url=None 表示启用自定义docs文档 
else:  # 开发环境 开启文档， 其中docs_url=None 表示启用自定义docs文档
    app = FastAPI(docs_url=None, redoc_url="/redos") 
app.mount("/static", StaticFiles(directory="static"), name="static")


if platform.system().lower() == "linux":
    PORT = 8085
else:
    PORT = 8085
origins = ['*']  # debug模式允许所有的主机访问
app.add_middleware(
    CORSMiddleware,  # 跨域的中间件
    allow_origins=origins,  # 允许跨域的网址列表
    allow_credentials=True,  # 允许跨域的 https
    allow_methods=["*"],  # 允许跨域的方法
    allow_headers=["*"],  # 允许跨域的请求头信息
)


"""加载子视图"""
app.include_router(router=iot_router, prefix="/iot")  # iot视图，
app.include_router(router=setting_router, prefix="/setting")  # 设置视图，
app.include_router(router=collect_router, prefix="/collect")  # 采集控制指令视图，



@app.get("/health", tags=["hello world"], response_class=Response, include_in_schema=INCLUDE_SCHEMA)
def hello_world_vew():
    """
    hello_world
    :return:
    """
    return Response("hello world!")



@app.get("/docs", include_in_schema=False)  # include_in_schema=False 不出现在自动文档中.
async def custom_swagger_ui_html(
    uid: str = QueryParam(None, title="当前日期", description="当前日期"),
):
    """
    自定义 swagger_ui的文档路由
    用于解决 swagger-ui-bundle.js 和  swagger-ui.css 有时候因为网络受限无法访问的问题
    swagger_ui 仓库地址: https://github.com/swagger-api/swagger-ui
    """
    today = date.today().strftime("%Y%m%d")
    if ENV != "dev" and uid != today:
        logger.warning(f"ENV = {ENV}, uid = {uid}, today = {today}")
        raise HTTPException(status_code=404, detail="not found")
    return get_swagger_ui_html(
        openapi_url=app.openapi_url,
        title=app.title + " - Swagger UI",
        oauth2_redirect_url=app.swagger_ui_oauth2_redirect_url,
        swagger_js_url="/static/js/swagger-ui-bundle.js",
        swagger_css_url="/static/css/swagger-ui.css",
    )



###############################  中间件和 自定义函数 ##################################


@app.middleware(middleware_type="http")
async def middleware_example(request: Request, call_next: Callable):
    """
    这是一个中间件，当前的设计使其相当于 flask的 before_request 和 after_request 之类的函数
    :param request: 请求体
    :param call_next: 原来的视图函数
    :return:
    """
    user_agent = request.headers.get("user-agent")
    if user_agent and 'censys.io' in user_agent:  # 屏蔽 censys.io 搜索引擎
        raise HTTPException(status_code=404, detail="404")
    # 取走 body （form和json都是body内容）会导致文件上传失败。
    # form = await request.form()
    # form_data = {k: v for k, v in form.items()}
    # json_data = {}
    # try:
    #     json_data = await request.json()
    # except:
    #     pass
    # 放行 
    t1 = time.perf_counter()
    resp = await call_next(request)
    t2 = time.perf_counter()
    d = round(t2 - t1, 6)
    if d > 0.3:
        logger.warning(f"请求路径: {request.url.path}，响应耗时: {d}秒")  
    else:
        logger.info(f"请求路径: {request.url.path}，响应耗时: {d}秒")  
    return resp


# 自定义 404 异常处理器  
@app.exception_handler(StarletteHTTPException)  
async def http_exception_handler(request: Request, exc: StarletteHTTPException):  
    if exc.status_code == 404:  
        # 记录日志  
        mes = f"请求404,ip:{get_fastapi_real_ip(request)}, host:{request.url.hostname}, path:{request.url.path}, authorization: {request.headers.get('authorization')}, referrer:{request.headers.get('referer')}, user_agent: {request.headers.get('user-agent')}"
        logger.error(mes)  
    # 你可以自定义返回给客户端的响应  
    return JSONResponse(  
        status_code=exc.status_code,  
        content={"detail": exc.detail}  
    )  


if __name__ == '__main__':
    import colorama
    color = colorama.Fore.CYAN
    print(color + "打印全部路由")
    for x in app.routes:
        logger.debug(color + getattr(x, "path").strip())
    logger.debug(color + "打印全部路由结束")
    logger.info(f"当前工作环境: {ENV}")
    if DEBUG:
        workers = 1
    else:
        workers = 3
    if platform.system().lower().startswith("win"):
        pass  # windows 下面不启动天勍的数据抓取服务
    else:
        # --处理天勍的数据请求--#
        # 先检查是否有已有节拍器线程，如果有，杀死
        BeaterThread.kill_old_thread()
        # 启动一个节拍器线程
        BeaterThread().start()
    # -- 加载之前没有做完的消息推送任务 --
    SensorOrm.load_push_tasks()
    # 以编码方式启动服务
    uvicorn.run(app="main_server:app", port=PORT, host="0.0.0.0", workers=workers, reload=False, log_level="info")
    pass